package com.pinfly.common.util;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.Hashtable;

import com.pinfly.common.http.HttpUtil;

/**
 * The URLUtil class provides a number of static methods that can be performed
 * on URLs.
 */
public class URLUtil
{
    // All methods are static, this should never be instantiated
    private URLUtil ()
    {
    }

    /**
     * Reads and executes the passed in URL, returning results of the page
     * execution as a String. Calls readURL(URL pageURL, boolean cache,
     * Hashtable postProps, Hashtable requestProps) passing null for postProps
     * and requestProps.
     * 
     * @param pageURL A URL to execute and read results from
     * @param cache A boolean that informs the remote server whether we prefer
     *            cached results. A value of false means that we don't want
     *            cached results, while true means we do. See the
     *            URLConnection.setUseCaches method for more information
     *            regarding this value.
     * @return Results of pageURL execution as a String.
     * @throws java.io.IOException When a failure occurs opening a URLConnection
     *             or reading and writing from the connection.
     * @see #readURL(URL,boolean,Hashtable,Hashtable)
     */
    public static String readURL (URL pageURL, boolean cache) throws java.io.IOException
    {
        return (readURL (pageURL, cache, null, null));
    }

    /**
     * Provides the ability to POST values to a URL, execute the URL, and return
     * the results of that execution as a String. Calls readURL(URL pageURL,
     * boolean cache, Hashtable postProps, Hashtable requestProps) passing null
     * for requestProps.
     * 
     * @param pageURL A URL to execute, post, and read results from
     * @param cache A boolean that informs the remote server whether we prefer
     *            cached results. A value of false means that we don't want
     *            cached results, while true means we do. See the
     *            URLConnection.setUseCaches method for more information
     *            regarding this value.
     * @param postProps A Hashtable that contains key/value Strings to post to
     *            the passed URL. When the posting is performed, each key and
     *            value is encoded using the java.net.URLEncoder.encode method.
     * @return Results of pageURL execution as a String.
     * @throws java.io.IOException When a failure occurs opening a URLConnection
     *             or reading and writing from the connection.
     * @see #readURL(URL,boolean,Hashtable,Hashtable)
     */
    public static String readURL (URL pageURL, boolean cache, Hashtable postProps) throws java.io.IOException
    {
        return (readURL (pageURL, cache, postProps, null));
    }

    /**
     * Provides the ability to POST values to a URL, set request header
     * properties, execute the URL, and return the results of that execution as
     * a String. <BR/>
     * <BR/>
     * 
     * Example: <BR/>
     * A user wishes to send a userid and username to the
     * "http://localhost/somepage.jsp" page and then process the results
     * returned by somepage.jsp. They want to make sure the remote webserver
     * does not send them cached results and wish to inform the remote webserver
     * that they are posting with a content type of
     * "application/x-www-form-urlencoded". <BR/>
     * <BR/>
     * 
     * The code would be similiar to the following: <BR/>
     * <BR/>
     * <CODE><PRE>
     * String urlResults;
     * URL url = new URL("http://localhost/somepage.jsp");
     * Hashtable postProps = new Hashtable();
     * Hashtable reqProps = new Hashtable();
     * 
     * postProps.put("userid","cbrown");
     * postProps.put("username","Charlie Brown");
     * 
     * reqProps.put("Content-Type", "application/x-www-form-urlencoded");
     * 
     * urlResults = URLUtil.readURL(url, false, postProps, reqProps);
     * 
     * processString(urlResults);
     * </PRE></CODE>
     * 
     * @param pageURL A URL to execute, post, and read results from
     * @param cache A boolean that informs the remote server whether we prefer
     *            cached results. A value of false means that we don't want
     *            cached results, while true means we do. See the
     *            URLConnection.setUseCaches method for more information
     *            regarding this value.
     * @param postProps A Hashtable that contains key/value Strings to post to
     *            the passed URL. When the posting is performed, each key and
     *            value is encoded using the java.net.URLEncoder.encode method.
     * @param requestProps A Hashtable that contains http request properties and
     *            values which will be sent in our http post. See the
     *            URLConnection.setRequestProperty method for more information.
     * @return Results of pageURL execution as a String.
     * @throws java.io.IOException When a failure occurs opening a URLConnection
     *             or reading and writing from the connection.
     */
    public static String readURL (URL pageURL, boolean cache, Hashtable postProps, Hashtable requestProps)
                                                                                                          throws java.io.IOException
    {
        URLConnection urlConn; // http connection to url
        DataOutputStream printout; // Stream to write to the page. (POST data)
        BufferedReader input; // Results from page execution.
        StringBuffer results; // results of url execution
        StringBuffer sendPost; // post parameters to send in
                               // key=value&key2=value2...
        boolean doPost = true; // whether we should post data or not
        Hashtable sendRequest; // request properties to send
        Object tmpKey; // key holder for hashtable
        Object tmpValue; // value holder for hashtable
        String line; // one line of data results from url execution
        String keyName; // a hashtable key name
        String keyValue; // a hashtable key value

        results = new StringBuffer ();
        sendRequest = new Hashtable ();
        sendPost = new StringBuffer ();

        // Determine whether we are posting
        if (postProps == null || postProps.isEmpty ())
        {
            doPost = false;
        }
        else
        {
            /*
             * We will be posting, so make sure that we at least will have a
             * "Content-Type" request property. Note that
             * application/x-www-form-urlencoded is the default mime type for an
             * http post. If the user specifies their own content-type, it will
             * override the below value.
             */
            sendRequest.put ("Content-Type", "application/x-www-form-urlencoded");

            /*
             * Build the posting string in format key1=value1&key2=value2 ...
             * URLencode keys and values
             */
            for (Enumeration key = postProps.keys (); key.hasMoreElements ();)
            {
                tmpKey = key.nextElement ();
                tmpValue = postProps.get (tmpKey);

                // Assume that keys and values are strings and encode them
                keyName = HttpUtil.URLEncode ((String) tmpKey);
                keyValue = HttpUtil.URLEncode ((String) tmpValue);

                // Add the key/value pair to our post
                sendPost.append (keyName + "=" + keyValue + "&");
            }

            // Remove the last ampersand
            sendPost.delete (sendPost.length () - "&".length (), sendPost.length ());

        }

        /*
         * Copy the passed request hashtable into ours, any defaults in ours
         * will be overridden by the user.
         */
        if (requestProps != null && !requestProps.isEmpty ())
        {
            for (Enumeration key = requestProps.keys (); key.hasMoreElements ();)
            {
                tmpKey = key.nextElement ();
                sendRequest.put (tmpKey, requestProps.get (tmpKey));
            }
        }

        // Open a connection to the page
        urlConn = pageURL.openConnection ();

        // If we need to post data, then inform that we will write to the
        // connection
        if (doPost)
        {
            urlConn.setDoInput (true);
        }

        // Inform that we will read from the connection
        urlConn.setDoOutput (true);

        if (cache)
        {
            // We want to use cache
            urlConn.setUseCaches (true);
        }
        else
        {
            // Don't use cache
            urlConn.setUseCaches (false);
        }

        // Set all request properties, assume strings
        if (sendRequest != null && !sendRequest.isEmpty ())
        {
            for (Enumeration key = sendRequest.keys (); key.hasMoreElements ();)
            {
                tmpKey = key.nextElement ();
                tmpValue = sendRequest.get (tmpKey);

                urlConn.setRequestProperty ((String) tmpKey, (String) tmpValue);
            }
        }

        // Send the post string
        if (doPost)
        {
            // Create stream that we can write to
            printout = new DataOutputStream (urlConn.getOutputStream ());

            // POST to the url and close stream
            printout.writeBytes (sendPost.toString ());
            printout.flush ();
            printout.close ();
        }

        // Get response from the url execution
        input = new BufferedReader (new InputStreamReader (urlConn.getInputStream ()));

        // Load response into results string buffer
        while (null != ((line = input.readLine ())))
        {
            results.append (line);
        }

        input.close ();

        // Return the results of the executed page
        return results.toString ();
    }

}
